﻿/*
 * Copyright © 2011 Tristan Reeves, treeves@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System.Collections.Generic;
using Sprinkles.Attributes;
using Sprinkles.Secondary;
using System;
using System.Linq;
using System.Reflection;

namespace Sprinkles
{
    public abstract class DeploymentHost : IDeploymentHost
    {
        IDeployment[] IDeploymentHost.GetDeployments()
        {
            //only if specified 'yes' do we go ahead and extract the deployment information
            if (!Include) return new IDeployment[0];

            m_Deployments.Clear();
            RegisterDeployments();
            return m_Deployments.ToArray();
        }
        Dictionary<string, Func<string>> IDeploymentHost.GetAliases()
        {
            return m_AliasList;
        }
        Dictionary<Type, Func<string>> IDeploymentHost.GetDefaultMappings()
        {
            return m_DefaultMappingsList;
        }
        void IDeploymentHost.SetContainerContext(object context)
        {
            m_ContainerContext = context;
        }
        void IDeploymentHost.SetObjectRetrievingCallback(Func<string, object> callback)
        {
            m_ObjectRetrievingCallback = callback;
        }
        void IDeploymentHost.SetUserContext(string context)
        {
            m_UserContext = context;
        }

        private Func<string, object> m_ObjectRetrievingCallback;
        private string m_UserContext;
        private object m_ContainerContext;
        private readonly List<IDeployment> m_Deployments = new List<IDeployment>();
        private readonly Dictionary<string, Func<string>> m_AliasList = new Dictionary<string, Func<string>>();
        private readonly Dictionary<Type, Func<string>> m_DefaultMappingsList = new Dictionary<Type, Func<string>>();

        public object ContainerContext
        {
            get { return m_ContainerContext; }
        }
        /// <summary>
        /// A setting passed in from the config file to indicate some kind of information to this object
        /// An example might be "test", or "prod"
        /// </summary>
        public string UserContext
        {
            get { return m_UserContext; }
        }
        protected void SetDefaultMapping(Type t, string name)
        {
            SetDefaultMapping(t, () => name);
        }
        protected void SetDefaultMapping<T>(string name)
        {
            SetDefaultMapping<T>(() => name);
        }
        protected void SetDefaultMapping<T>(Func<string> map)
        {
            SetDefaultMapping(typeof(T), map);
        }
        protected void SetDefaultMapping(Type t, Func<string> map)
        {
            m_DefaultMappingsList.Add(t, map);
        }

        protected internal void SetAlias(string alias, string orginal)
        {
            SetAlias(alias, () => orginal);
        }
        protected internal void SetAlias(string alias, Func<string> forginal)
        {
            m_AliasList.Add(alias, forginal);
        }
        protected void ScanAssemblyForAttributeBasedRegistration(Assembly assembly)
        {
            foreach (var type in assembly.GetTypes())
            {
                var exportAttribute = type
                    .GetCustomAttributes(typeof(ExportAttribute), false)
                    .OfType<ExportAttribute>()
                    .FirstOrDefault();
                if (exportAttribute == null) continue;

                var refName = exportAttribute.Name;
                var isSingleton = exportAttribute.IsSingleton;
                var isLazyInit = exportAttribute.IsLazyInit;
                var scope = exportAttribute.Scope;
                var defaultForTypes = exportAttribute.DefaultForTypes;
                if (exportAttribute.IsDefaultForThisType)
                {
                    defaultForTypes = defaultForTypes.Union(new[] { type }).Distinct().ToArray();
                }

                var properties = type
                    .GetProperties()
                    .Where(p => p.IsDefined(typeof(ImportAttribute), true))
                    .Select(
                        p => new
                        {
                            Property = p,
                            ImportAttribute = p
                                .GetCustomAttributes(typeof(ImportAttribute), true)
                                .OfType<ImportAttribute>()
                                .First()
                        }
                    ).ToArray();

                var dependencyInfos = properties.Select(
                    x => new PropertyImportInfo()
                             {
                                 ReflectionInfo = x.Property,
                                 ImportResolutionType = x.ImportAttribute.ResolutionType,
                                 Data = x.ImportAttribute.GetReferenceValue()
                             }
                    ).ToArray();

                ConstructorInfo specifiedCI = null;
                var argumentInfo = new List<ConstructorArgumentImportInfo>();
                var constructors = type.GetConstructors();
                foreach (var ci in constructors)
                {
                    if (!ci.IsDefined(typeof(UseConstructorAttribute), false)) continue;
                    if (specifiedCI != null)
                    {
                        var msg = "Multiple constructors for type [" + type.FullName + "] had UseConstructor attribute defined.";
                        throw new InvalidOperationException(msg);
                    }
                    specifiedCI = ci;

                    var parameters = ci.GetParameters();
                    if (!parameters.All(pi => pi.IsDefined(typeof(ImportAttribute), false)))
                    {
                        var msg = "Constructor for type [" + type.FullName +
                                  "] had some import attributes defined. It is required that all parameters have such an attribute defined in order for DI to work.";
                        throw new InvalidOperationException(msg);
                    }

                    foreach (var parameterInfo in parameters)
                    {
                        var attr = parameterInfo
                            .GetCustomAttributes(typeof(ImportAttribute), true)
                            .OfType<ImportAttribute>()
                            .First();

                        var info = new ConstructorArgumentImportInfo()
                        {
                            Data = attr.GetReferenceValue(),
                            ImportResolutionType = attr.ResolutionType,
                            ReflectionInfo = parameterInfo
                        };
                        argumentInfo.Add(info);
                    }
                }

                this.RegisterTypeFromAttributes(
                    type, isSingleton, isLazyInit, scope, refName, defaultForTypes,
                    dependencyInfos, specifiedCI, argumentInfo.ToArray());
            }
        }

        public Deployment<T> Register<T>()
        {
            var result = new Deployment<T>();
            result.ContainerContext = this.ContainerContext;
            result.ObjectRetrievingCallback = this.m_ObjectRetrievingCallback;
            result.DeploymentHost = this;
            m_Deployments.Add(result);
            return result;
        }
        public Deployment<T> Register<T>(string name)
        {
            return Register<T>().Name(name);
        }
        protected virtual bool Include
        {
            get { return true; }
        }
        protected abstract void RegisterDeployments();
    }
}
